package jmine.tec.web.userInputFilter;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
 * Classe que contem a coleção de filtros de entrada.
 * 
 * @author pedro
 */
public class UserInputFilterHolder {
    private static final String CHARSET = "charset=";
    public static final String CHARACTER_ENCODING_KEY = "javax.faces.request.charset";

    private List<UserInputFilter> userInputFilters;

    /**
     * Construtor
     * 
     * @param userInputFilters lista de filtros a serem aplicados
     */
    public UserInputFilterHolder(List<UserInputFilter> userInputFilters) {
        this.userInputFilters = userInputFilters;
    }

    /**
     * Filtra o request de acordo com os filtros registrados
     * 
     * @param servletRequest request a ser filtrada
     * @throws UnsupportedEncodingException se o conteudo passado estiver em um encoding nao suportado
     */
    public void filterInput(ServletRequest servletRequest) throws UnsupportedEncodingException {
        this.setupRequestEncoding((HttpServletRequest) servletRequest);

        Map<?, ?> parameters = servletRequest.getParameterMap();

        for (UserInputFilter filter : this.userInputFilters) {
            this.updateRequestParameters(parameters, filter);
        }
    }

    /**
     * XXX Rafael: Trecho de codigo extraido de {@link org.ajax4jsf.webapp.BaseFilter} =((. Configura o encoding do request antes de ler os
     * parametros passados.
     * 
     * @param httpServletRequest request a ser filtrada
     * @throws UnsupportedEncodingException se o conteudo passado estiver em um encoding nao suportado
     */
    protected void setupRequestEncoding(HttpServletRequest httpServletRequest) throws UnsupportedEncodingException {
        String contentType = httpServletRequest.getHeader("Content-Type");

        String characterEncoding = this.lookupCharacterEncoding(contentType);

        if (characterEncoding == null) {
            HttpSession session = httpServletRequest.getSession(false);

            if (session != null) {
                characterEncoding = (String) session.getAttribute(CHARACTER_ENCODING_KEY);
            }

            if (characterEncoding != null) {
                httpServletRequest.setCharacterEncoding(characterEncoding);
            }
        }
    }

    /**
     * XXX Rafael: Trecho de codigo extraido de {@link org.ajax4jsf.webapp.BaseFilter} =((. Detect request encoding from Content-Type
     * header.
     * 
     * @param contentType contentType
     * @return - charset, if present.
     */
    private String lookupCharacterEncoding(String contentType) {
        String characterEncoding = null;

        if (contentType != null) {
            int charsetFind = contentType.indexOf(CHARSET);
            if (charsetFind != -1) {
                if (charsetFind == 0) {
                    // charset at beginning of Content-Type, curious
                    characterEncoding = contentType.substring(CHARSET.length());
                } else {
                    char charBefore = contentType.charAt(charsetFind - 1);
                    if (charBefore == ';' || Character.isWhitespace(charBefore)) {
                        // Correct charset after mime type
                        characterEncoding = contentType.substring(charsetFind + CHARSET.length());
                    }
                }
            }
        }
        return characterEncoding;
    }

    /**
     * Devolve uma lista com as chaves onde o objeto associado é uma string;
     * 
     * @param parametersMap mapa que representa os parâmetros do request
     * @param filter {@link UserInputFilter}
     * @return Lista contendo as chaves que guardam strings
     */
    private List<Object> extractKeysToFilter(Map<?, ?> parametersMap, UserInputFilter filter) {
        List<Object> keysToFilter = new ArrayList<Object>();

        Object value;
        for (Object key : parametersMap.keySet()) {
            value = parametersMap.get(key);
            if (this.mustBeFiltered(key, value, filter)) {
                keysToFilter.add(key);
            }
        }
        return keysToFilter;
    }

    /**
     * Método que devolve se o parâmetro deve ser filtrado ou não. de parametros a serem filtrados ou não
     * 
     * @param key chave do mapa de parâmetros
     * @param value valor do mapa de parâmetros
     * @param filter filtro que poderá ser aplicado ou não
     * @return se o parâmetro deve ser filtrado ou não.
     */
    private boolean mustBeFiltered(Object key, Object value, UserInputFilter filter) {

        if (value == null || !(value instanceof String[])) {
            return false;
        }

        if (!(key instanceof String)) {
            return false;
        }

        String keyStr = (String) key;
        for (String fieldToPreserv : filter.getPreserveList()) {
            if (keyStr.contains(fieldToPreserv)) {
                return false;
            }
        }

        if (this.isXmlContent((String[]) value)) {
            return false;
        }

        return true;
    }

    /**
     * Devolve se algum elemento do array representa um arquivo xml ou não.
     * 
     * @param params parametros do request
     * @return se algum elemento do array representa um arquivo xml ou não.
     */
    private boolean isXmlContent(String[] params) {
        boolean xmlContent = false;

        for (String param : params) {
            if (param.toLowerCase().contains("<?xml version=")) {
                xmlContent = true;
                break;
            }
        }
        return xmlContent;
    }

    /**
     * Altera os valores do mapa (reassocia a posição do vetor a uma nova string com o filtro aplicado).
     * 
     * @param parameters mapa de parâmetros do Request
     * @param filter Filtro a ser aplicado ao parameter do request
     */
    private void updateRequestParameters(Map parameters, UserInputFilter filter) {
        List stringParams = this.extractKeysToFilter(parameters, filter);

        for (Object key : stringParams) {
            String[] params = (String[]) parameters.get(key);
            for (int i = 0; i < params.length; i++) {
                params[i] = filter.applyFilter(params[i]);
            }
        }
    }

}
